# 变量命名特点：所有单个单词首字母小写，所有两个单词连写则第一个首字母小写，第二个首字母大写。
class Vertex:
    '''将每个棋盘状况做成节点，包括当前轮到谁的信息等等。'''

    def __init__(self, board, belong, player, currentRound=None, mode=None, parent=None, action=None):
        self.board = board  # 棋盘
        self.belong = belong  # 当前棋局（即将）行动方
        self.player = player  # 玩家自己（即Player类）
        self.round = currentRound  # 当前轮数
        self.mode = mode  # 当前模式
        self.parent = parent  # 父节点
        self.child = {}  # 子节点的字典：{做出的行动:子节点}
        self.action = action  # 用于存放到达该节点的行动
        self.goal = 0  # goal,aj_value用来保存父节点落子在对面的原因，目前是为后手方准备的属性
        self.aj_value = 0
        self.alpha = float('-inf')  # 下界
        self.beta = float('inf')  # 上界
        self.boardValue = None  # 静态估值函数，先用这个凑合一下，要更改函数只需要改此一处。
        self.searchValue = None  # 搜索后估值
        self.bestchoice = None  # （行动方）最佳策略
        self.winner = None  # 胜利方

    def getWinner(self):  # 判断输赢
        myself = tuple(reversed(self.board.getScore(self.player.belong)))
        myRival = tuple(reversed(self.board.getScore(not self.player.belong)))
        if myself >= myRival:  # 平局算自己赢
            return self.player.belong
        else:
            return not self.player.belong

    def getNone(self):  # 重写这个方法是为了获得一个顺序更合理的位置列表 5.25日
        if self.belong:
            return [(i, j) for j in range(4, 8) for i in range(4) if not self.board.getValue((i, j))]
        else:
            return [(i, j) for j in range(3, -1, -1) for i in range(4) if not self.board.getValue((i, j))]

    def setRange(self, alpha, beta):
        self.alpha = alpha
        self.beta = beta

    def tru_expand(self):
        """针对先手模式的expand函数,选择在对方的落子位置时没有像反手那样详细讨论，因为先手模式己方率先移动，有主动权。同时也省下一些时间，
        便于在特殊情况搜索到3。之前的先手模式有两个问题:1是喜欢将子落在对方位置(因为估值函数中设置了一个很大的威胁分)，但实际上存在落子在对面
        后发现吃掉不划算的情况(因为搜索深度为2的话只能搜到该自己移动的局面，到自己移动时，AI可能发现吃掉自己刚刚落在对面的2不划算，因为有可能移过去
        后自己的棋会被对面吃),这样就白白地送分了。2是在己方棋子受威胁时，因为落子阶段如果搜索深度为2的话会导致AI只能搜索到自己移动的局面，针对己方
        棋子吃不到对面，但对面可以吃己方棋子，且己方棋子无靠山的情形，估值函数返回的是-2**x的分数，如果自己移动时没有能让这个棋子跑掉的方法，就又要
        白给了(目前的估值函数，落子在对面的损失分至少是2+3+8=13，因此要使得AI落子在对面的话，至少要x>=4才行)，但是，如果提高威胁分的话，可能发生的
        情况是：本来自己的被威胁棋能够跑掉，但AI仍选择落子在对面，这样又白白地送分了。以上的分析表明在落子阶段将深度搜索到3是一种可能的解决方法。"""

        if self.mode == 'position':  # 时间上进行了优化，只需要copy一次了
            another = self.board.getNext(self.belong, self.round)
            available = self.getNone()
            if another != ():
                imitation = self.board.copy()
                imitation.add(self.belong, another)  # 棋盘模拟
                vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self,
                                action=another)  # 各种参数设置
                self.child[another] = vertex  # 建立子节点关联
                vertex.mode = 'position' if self.belong else 'direction'  # 模式设置

            if available:
                bestPos = max(available,
                              key=lambda pos: self.player.cost_function_1(self.board, pos, not self.belong))
                imitation = self.board.copy()
                imitation.add(self.belong, bestPos)
                vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self, action=bestPos)
                self.child[bestPos] = vertex
                vertex.mode = 'position' if self.belong else 'direction'

            if self.child == {}:  # 考虑到无棋可走的可能
                if self.belong:  # 此时游戏结束。
                    self.winner = self.getWinner()
                    return  # 省时间
                imitation = self.board.copy()
                vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self, action=())
                self.child[()] = vertex
                vertex.mode = 'position' if self.belong else 'direction'

        elif self.mode == 'direction':
            directionList = [3, 2, 1, 0] if self.belong else [2, 3, 1, 0]
            for direction in directionList:
                imitation = self.board.copy()
                if imitation.move(self.belong, direction):
                    vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self, action=direction)
                    self.child[direction] = vertex
                    vertex.mode = 'direction' if self.belong else 'position'
                    vertex.round += 0 if self.belong else 1  # 如果是后手，子节点回合数将+1
            if self.child == {}:
                imitation = self.board.copy()
                vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self, action=())
                self.child[()] = vertex
                vertex.mode = 'direction' if self.belong else 'position'
                vertex.round += 0 if self.belong else 1
        else:
            return

    def fal_expand(self):  # 针对后手模式的expand
        if self.mode == 'position':
            another = self.board.getNext(self.belong, self.round)
            available = self.getNone()
            if another != ():
                imitation = self.board.copy()
                imitation.add(self.belong, another)  # 棋盘模拟
                vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self,
                                action=another)  # 各种参数设置
                self.child[another] = vertex  # 建立子节点关联
                vertex.mode = 'position' if self.belong else 'direction'  # 模式设置

            if available:
                now_chess = self.board.getRaw()
                all_pos = [(self.player.new_cost_1(now_chess, pos, not self.belong), pos) for pos in available]
                all_threaten_chess = [(x[0][1], x[1]) for x in all_pos if
                                      x[0][0] == float("inf") and (
                                          not self.player.mychess_is_safe(x[0][2], self.belong, now_chess))]
                # 这里new_cost_1 将返回((score,落子点对应的相邻棋子分值,该块的位置),落子点位置)
                # all_threaten_chess的元素为(落子点对应相邻棋子分值,落子点位置)

                if all_threaten_chess:  # 对应存在己方棋子受到威胁,取受威胁棋子分最大的落子点
                    k = max(all_threaten_chess, key=lambda y: y[0])
                    bestPos = k[1]
                    imitation = self.board.copy()
                    imitation.add(self.belong, bestPos)
                    vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self, action=bestPos)
                    vertex.goal = 3  # 表明落子在对面的原因是为了保护己方的值为aj_value的棋子
                    vertex.aj_value = k[0]
                    self.child[bestPos] = vertex
                    vertex.mode = 'position' if self.belong else 'direction'
                else:
                    k = max(all_pos, key=lambda y: y[0][0])
                    bestPos = k[1]
                    imitation = self.board.copy()
                    imitation.add(self.belong, bestPos)
                    vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self, action=bestPos)
                    self.child[bestPos] = vertex
                    vertex.goal = 0 if k[0][0] == float("inf") else 1
                    # goal=3表明己方有受威胁棋子，goal=1表明己方无受威胁棋子
                    vertex.mode = 'position' if self.belong else 'direction'

            if self.child == {}:  # 考虑到无棋可走的可能
                if self.belong:  # 此时游戏结束。
                    self.winner = self.getWinner()
                    return  # 省时间
                imitation = self.board.copy()
                vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self, action=())
                self.child[()] = vertex
                vertex.mode = 'position' if self.belong else 'direction'

        elif self.mode == 'direction':
            directionList = [3, 2, 1, 0] if self.belong else [2, 3, 1, 0]
            for direction in directionList:
                imitation = self.board.copy()
                if imitation.move(self.belong, direction):
                    vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self,
                                    action=direction)
                    self.child[direction] = vertex
                    vertex.mode = 'direction' if self.belong else 'position'
                    vertex.round += 0 if self.belong else 1  # 如果是后手，子节点回合数将+1
            if self.child == {}:
                imitation = self.board.copy()
                vertex = Vertex(imitation, not self.belong, self.player, self.round, parent=self, action=())
                self.child[()] = vertex
                vertex.mode = 'direction' if self.belong else 'position'
                vertex.round += 0 if self.belong else 1
        else:
            return

    def fal_dir_depth_0(self):  # 针对后手的移动阶段的depth=0的情况
        prospects = self.player.new_cost_function(self.board)
        if prospects[1] > self.player.startValue and self.round < 400:
            act = self.parent.parent.action
            if (act != () and 0 <= act[1] <= 3) or (prospects[1] - self.player.startValue) > 4:
                self.boardValue = self.player.basic_list[11]
                self.alpha = self.boardValue
                if self.alpha >= self.beta:
                    return False
                else:
                    self.searchValue = self.boardValue
                    return True
        if self.parent.goal:  # 说明我方AI把棋落在了对面
            """goal=3表明落子为了保护己方受威胁棋子，这个棋子的分值至少是4，如果分值恰好为4，被吃的损失分至少为4+16+8=28，而落子在对面
            的损失分至多为3+2+8+8=21，因此如果AI选择将棋落在己方，一定是因为对方吃掉己方的4后会相应地被己方吃棋，这样己方至多损失一个2分。
            但是将棋落在对方也会至少损失2分。因此在后手且棋子分值为4时，AI必能做出正确决策。记被保护的棋子指数为x>=3时，被吃的损失分至少为
            (x+5)*2**x,此时假设己方能吃到一个指数为y的对手棋子,威胁贡献分为(y+16)*2**y，如果y<=x-1,且AI选择将棋落在己方，这是错误决策
            将落子在对方位置的损失分考虑为17分,这表明 (x+5)*2**x<=(x+15)*2**(x-1)+17,补上2**(x+2)后，上式不再成立，且当y>=x时，
            满足(y+16)*2**y>(x+9)*2**x"""
            """后手的一个缺陷在于落子在己方的位置时有可能恰好会撞在对面的枪口上，但是移动时的搜索会搜到这种情况，目前的估值函数会使得AI落子在对面，
            这样会损失至少3+2+8=13分，这种机制使得AI为了在移动时优先选择使得落子位置不撞在枪口上的移动方向。但是如果某种移动方向使得AI能获得>13分
            的激励，则AI就可能会最终把子落在对面了，这是错误决策。考虑两种AI做出错误移动方向的原因:错误移动方向能够将两个16合成32，或者错误移动方向
            使得两个16在一块了。这两种情况都会使AI获得16分的激励。我们将goal=1时落子在对面的代价增加4分，可以避免这两种情况(会不会造成其它的负面
            影响呢？)"""
            """最后一种是落子在对面保护己方棋子，但己方棋子有靠山的情形，设该棋子指数为x>=2，如果对面棋子能够吃掉己方棋子(说明对面棋子有靠山),目前
            的估值函数会返回-x-2<=-4的威胁分，再考虑吃掉本身带来的至少-28的反馈，则AI必定会选择落子在对面。如果对面棋子没有靠山，则目前的估值函数
            会反馈+x分，落子在对面会失去这个x分的反馈。总之，在这种情况下AI总是能够正确决策。"""
            if self.parent.goal == 3:
                if self.parent.aj_value >= 3:
                    self.boardValue = prospects[0] + 4 * self.player.basic_list[self.parent.aj_value]

            elif self.parent.goal == 1:
                self.boardValue = prospects[0] - 4
            self.alpha = self.boardValue
            if self.alpha >= self.beta:
                return False
            else:
                self.searchValue = self.boardValue
                return True

        self.boardValue = prospects[0]
        self.alpha = self.boardValue
        if self.alpha >= self.beta:
            return False
        else:
            self.searchValue = self.boardValue
            return True

    def tru_dir_depth_0(self):  # 针对先手的移动阶段的depth=0的情况,还未进行修改
        prospects = self.player.new_cost_function(self.board)
        if prospects[1] > self.player.startValue and self.round < 400:
            if (self.action != () and 4 <= self.action[1] <= 7) or (prospects[1] - self.player.startValue) > 4:
                self.boardValue = self.player.basic_list[11]
                self.alpha = self.boardValue
                if self.alpha >= self.beta:
                    return False
                else:
                    self.searchValue = self.boardValue
                    return True

        self.boardValue = prospects[0]
        self.alpha = self.boardValue
        if self.alpha >= self.beta:
            return False
        else:
            self.searchValue = self.boardValue
            return True

    def fal_pos_depth_0(self):  # 针对后手的落子阶段的depth=0的情况
        prospects = self.player.new_cost_function(self.board)
        if self.parent.goal == 3:  # 这里的讨论是为了与fal_dir_depth_0 保持一致
            if self.parent.aj_value >= 3:
                self.boardValue = prospects[0] + 4 * self.player.basic_list[self.parent.aj_value]
            else:
                self.boardValue = prospects[0]
        elif self.parent.goal == 1:
            self.boardValue = prospects[0] - 4
        else:
            self.boardValue = prospects[0]
        self.alpha = self.boardValue
        if self.alpha >= self.beta:
            return False
        else:
            self.searchValue = self.boardValue
            return True

    def tru_pos_depth_0(self):  # 针对先手的落子阶段的depth=0的情况
        pass

    def search(self, depth):
        '''相应深度的alpha-beta搜索，同时返回搜索有效性。所谓有效性，指它能否严格更新父节点的需求值。'''
        if depth >= 1:
            if self.round >= 500:  # 此时游戏结束。
                self.winner = self.getWinner()
            if self.winner == True or self.winner == False:  # 胜负已分，估值直接判定为正/负无穷。
                if self.winner == self.player.belong:
                    self.boardValue = self.player.basic_list[12]
                else:
                    self.boardValue = -self.player.basic_list[12]
                if self.player.belong == self.belong:
                    self.alpha = self.boardValue
                else:
                    self.beta = self.boardValue
                if self.alpha >= self.beta:
                    return False
                else:
                    self.searchValue = self.boardValue
                    return True

            self.expand()  # 生成子节点  (放到后面来，省时间)
            bestchoice = None
            for move in self.child:
                self.child[move].setRange(self.alpha, self.beta)
                if self.child[move].search(depth - 1):  # 递归地深度搜索
                    if self.player.belong == self.belong:
                        self.alpha = self.child[move].searchValue
                    else:
                        self.beta = self.child[move].searchValue
                    bestchoice = move
                if self.alpha >= self.beta:  # 这里是大于等于，意味着相同估值时，先遍历的优先。
                    return False  # 此时搜索无效
                # 如果子节点全部返回False，表示它的真实值一定大于beta或小于alpha,所以它的更新是一定成功的。
                # 投机取巧地，它拿≥beta或≤alpha的真实值并返回True会导致它的父节点返回False。所以它拿β或α效果是一样的。
            self.bestchoice = bestchoice
            if self.player.belong == self.belong:
                self.searchValue = self.alpha
            else:
                self.searchValue = self.beta
            return True

        elif depth == 0:  # 递归的最简情况,增加了总分增加时的优先判断
            if self.player.mode == "direction":
                return self.direction_depth_0()
            else:
                return self.pos_depth_0()

        else:
            return False


class Player:
    def __init__(self, isFirst: bool, array: list) -> None:
        self.belong = isFirst
        self.array = array
        self.height = 4
        self.width = 8
        self.half_width = 4
        self.basic_list = [int(pow(2, n)) for n in range(11)]
        self.value_list = [n * self.basic_list[n] for n in range(11)]  # 预先维护估分表
        self.basic_list.append(int(pow(2, 20)))  # 两个会用到的分值
        self.basic_list.append(int(pow(2, 21)))
        # self.empty_score = 12  # 在代价函数中每个空位对应的得分，12分等价于一个数字为8的格子的得分
        # self.rival_empty_score = 12  # 本次修改新增的属性，鼓励AI将对方的棋子吃掉后将棋子移回本方
        """移除了空位得分 5.24日"""

    def output(self, currentRound, board, mode):
        if mode == "position" or "direction":
            self.startValue = self.get_chess_score(board)
            self.mode = mode
            depth = 4 if mode == "direction" else 2
            self.searchTree = Vertex(board, self.belong, self, currentRound, mode, parent=None)
            self.searchTree.search(depth)
            return self.searchTree.bestchoice
        else:
            return

    def get_chess_score(self, board):  # 返回目前棋盘双方的子力差
        chess_1 = map(lambda x: self.basic_list[x], board.getScore(self.belong))
        chess_2 = map(lambda x: self.basic_list[x], board.getScore(not self.belong))
        return sum(chess_1) - sum(chess_2)

    def new_cost_1(self, now_chess, position: tuple, belong: bool):
        score = 0
        if self.belong == belong:  # 表明棋子下在自己这边
            up = self.get_neighbor_2(now_chess, position, 0)  # 获取该位置上方的棋子值
            down = self.get_neighbor_2(now_chess, position, 1)  # 获取该位置下方的棋子值
            left = self.get_neighbor_2(now_chess, position, 2)  # 获取该位置左方的棋子值
            right = self.get_neighbor_2(now_chess, position, 3)  # 获取该位置右方的棋子值
            score += self.put_chessman_score(up, down, left, right)
            return score
        else:  # 表明棋子下在对方那,此时棋子属于对方
            up = self.get_neighbor_3(now_chess, position, 0)  # 获取该位置上方的棋子值
            down = self.get_neighbor_3(now_chess, position, 1)  # 获取该位置下方的棋子值
            left = self.get_neighbor_3(now_chess, position, 2)  # 获取该位置左方的棋子值
            right = self.get_neighbor_3(now_chess, position, 3)  # 获取该位置右方的棋子值
            score += self.put_chessman_score(up, down, left, right)
            return score

    def cost_function_1(self, chessboard, position: tuple, belong: bool):  # 对应mode为放棋子的情况,第一个代价函数
        if self.belong == belong:  # 表明棋子下在自己这边
            up = self.get_neighbor_2(chessboard, position, 0)  # 获取该位置上方的棋子值
            down = self.get_neighbor_2(chessboard, position, 1)  # 获取该位置下方的棋子值
            left = self.get_neighbor_2(chessboard, position, 2)  # 获取该位置左方的棋子值
            right = self.get_neighbor_2(chessboard, position, 3)  # 获取该位置右方的棋子值
            return self.put_chessman_score(up, down, left, right)

        else:  # 表明棋子下在对方那,此时棋子属于对方
            up = self.get_neighbor_3(chessboard, position, 0)  # 获取该位置上方的棋子值
            down = self.get_neighbor_3(chessboard, position, 1)  # 获取该位置下方的棋子值
            left = self.get_neighbor_3(chessboard, position, 2)  # 获取该位置左方的棋子值
            right = self.get_neighbor_3(chessboard, position, 3)  # 获取该位置右方的棋子值
            return self.put_chessman_score(up, down, left, right)

    def new_cost_function(self, chessboard):  # 考虑情况会更全面一点,效率也会高一些
        score = [0]
        my_position_dict = {(i, j): [True, True, True, True] for i in range(4) for j in range(8) if
                            self.belong == chessboard.getBelong((i, j))}
        neighbor_value = {(i, j, direction): -2 for i in range(4) for j in range(8) for direction in range(4)}
        # 列表中的整数表示该方向上的相邻同类棋子的值
        now_chess = chessboard.getRaw()
        another_score = 0

        for position in self.return_pos(self.belong):  # 对己方的16个格子进行遍历
            b = now_chess[position[0]][position[1]][0]  # 拿到value
            a = self.basic_list[b]
            if now_chess[position[0]][position[1]][1] != self.belong:  # 说明己方位置被对方占了
                score[0] -= a * 16  # 提高被占领分来避免AI送自己子给对面吃了
                score[0] -= a + 1  # 对手棋子分（暂时沿用上一个）
                another_score -= a
            else:
                if b:
                    for direction in range(4):
                        self.new_situation_score(score, position, direction, my_position_dict,
                                                 now_chess, neighbor_value)  # 形势分
                    score[0] += b * a  # 己方棋子分
                    another_score += a

        for position in self.return_pos(not self.belong):  # 对对方的16个格子进行遍历
            b = now_chess[position[0]][position[1]][0]
            a = self.basic_list[b]
            if now_chess[position[0]][position[1]][1] == self.belong:  # 说明对方位置被己方占了
                score[0] += b  # 占领对面的得分
                score[0] += b * a  # 自己棋子分
                another_score += a
                for direction in range(4):
                    self.new_situation_score(score, position, direction,
                                             my_position_dict, now_chess, neighbor_value)  # 形势分
            else:
                if b:
                    score[0] -= a + 1  # 对手棋子分
                    another_score -= a
        return score[0] + another_score * 2, another_score

    def new_situation_score(self, score, position, direction, my_position_dict,
                            now_chess, neighbor_value):  # 落子阶段调用(此时该对手下棋)？此时互吃情形应当返回负分,（减少了重复计算）
        if not my_position_dict[position][direction]:
            return  # 已经遍历过了

        is_adjacent = True  # 该参数用于判断position是否安全
        next_position = self.update_position(position, direction)
        while self.is_in_chess(next_position) and (now_chess[next_position[0]][next_position[1]][0] == 0) \
                and self.belong == now_chess[next_position[0]][next_position[1]][1]:
            is_adjacent = False  # 表明该棋子在该方向上没有直接相邻敌对格，因此不存在被吃风险,是安全的
            next_position = self.update_position(next_position, direction)
        if not self.is_in_chess(next_position):
            my_position_dict[position][direction] = False
            neighbor_value[(position[0], position[1], direction)] = -1  # 表示这个方向上没有己方棋
            return  # 此时该棋子的该方向上没有棋子,返回0分

        elif self.belong != now_chess[next_position[0]][next_position[1]][1]:
            if now_chess[next_position[0]][next_position[1]][0]:  # 这表明不仅格子属于对方，而且上面还有棋
                if is_adjacent:  # 表明初始位置和最终的next_position相邻
                    if now_chess[next_position[0]][next_position[1]][0] == now_chess[position[0]][position[1]][0]:
                        # 详细讨论这种情况
                        my_position_dict[position][direction] = False
                        neighbor_value[(position[0], position[1], direction)] = -1
                        a = self.get_direction(direction)
                        lst = [self.judge_is_safe(next_position, dire, now_chess,
                                                  neighbor_value) for dire in range(4) if dire != a]

                        if now_chess[next_position[0]][next_position[1]][0] + 1 in lst:
                            # 说明对面有靠山，返回负分
                            x = now_chess[position[0]][position[1]][0]
                            score[0] -= x + 2  # 返回负分过小的隐患是存在自己跑不掉的情况，这时AI大概率不会落子堵住对面冲过来
                            return

                        x = now_chess[position[0]][position[1]][0]
                        score[0] += self.value_list[x] + 16 * self.basic_list[x]
                        return

                    else:
                        my_position_dict[position][direction] = False
                        neighbor_value[(position[0], position[1], direction)] = -1
                        return
                else:
                    if now_chess[next_position[0]][next_position[1]][0] == now_chess[position[0]][position[1]][0]:
                        my_position_dict[position][direction] = False
                        neighbor_value[(position[0], position[1], direction)] = -1
                        x = now_chess[position[0]][position[1]][0]
                        # 分析到底能不能白吃
                        a = self.get_direction(direction)
                        lst = [self.judge_is_safe(next_position, dire, now_chess,
                                                  neighbor_value) for dire in range(4) if dire != a]
                        if x + 1 in lst:  # 对面有靠山
                            return
                        else:
                            score[0] += self.value_list[x] + 16 * self.basic_list[x]
                            return  # 可以白吃情形
                    else:
                        my_position_dict[position][direction] = False
                        neighbor_value[(position[0], position[1], direction)] = -1
                        return
            else:
                while self.is_in_chess(next_position) and now_chess[next_position[0]][next_position[1]][0] == 0:
                    next_position = self.update_position(next_position, direction)
                if not self.is_in_chess(next_position):  # 该方向上无棋
                    my_position_dict[position][direction] = False
                    neighbor_value[(position[0], position[1], direction)] = -1
                    return

                elif self.belong == now_chess[next_position[0]][next_position[1]][1]:  # 己方的棋
                    my_position_dict[position][direction] = False
                    my_position_dict[next_position][self.get_direction(direction)] = False
                    neighbor_value[(position[0], position[1], direction)] = -1
                    neighbor_value[(next_position[0], next_position[1], self.get_direction(direction))] = -1
                    # 中间间隔对手空格，不能相互呼应
                    return
                else:  # 对面的棋
                    if now_chess[next_position[0]][next_position[1]][0] == now_chess[position[0]][position[1]][0] \
                            and is_adjacent:  # 有被吃的风险
                        my_position_dict[position][direction] = False
                        neighbor_value[(position[0], position[1], direction)] = -1
                        # 需要详细讨论
                        lst = [self.get_neighbor_value(score, position, dire, my_position_dict, now_chess,
                                                       neighbor_value) for dire in range(4) if dire != direction]
                        x = now_chess[position[0]][position[1]][0]
                        if x + 1 in lst:
                            score[0] += x  # 有靠山就返回一个正分
                        else:
                            score[0] -= self.basic_list[now_chess[position[0]][position[1]][0]]

                    else:
                        my_position_dict[position][direction] = False
                        neighbor_value[(position[0], position[1], direction)] = -1
                        return
        else:
            if now_chess[next_position[0]][next_position[1]][0] == now_chess[position[0]][position[1]][0]:
                my_position_dict[position][direction] = False
                my_position_dict[next_position][self.get_direction(direction)] = False
                neighbor_value[(position[0], position[1], direction)] = now_chess[next_position[0]][next_position[1]][0]
                neighbor_value[(next_position[0], next_position[1], self.get_direction(direction))] = \
                    now_chess[position[0]][position[1]][0]
                if now_chess.count((0, self.belong)) <= 1:  # 当空位很少时，相邻的同类棋子变得很重要
                    score[0] += self.basic_list[now_chess[next_position[0]][next_position[1]][0] + 1] - 1
                    return
                score[0] += self.basic_list[now_chess[next_position[0]][next_position[1]][0]]
                return
            else:
                my_position_dict[position][direction] = False
                my_position_dict[next_position][self.get_direction(direction)] = False
                neighbor_value[(position[0], position[1], direction)] = now_chess[next_position[0]][next_position[1]][0]
                neighbor_value[(next_position[0], next_position[1], self.get_direction(direction))] = \
                    now_chess[position[0]][position[1]][0]
                return

    def get_neighbor_2(self, now_chess, position: tuple, direction: int):
        """调用这个函数意味着落子方是not self.belong,他正在分析将棋子落在self.belong时的局势"""
        next_position = self.update_position(position, direction)
        while self.is_in_chess(next_position) and (now_chess[next_position[0]][next_position[1]][0] == 0) \
                and self.belong == now_chess[next_position[0]][next_position[1]][1]:
            next_position = self.update_position(next_position, direction)
        if self.is_in_chess(next_position):
            be = now_chess[next_position[0]][next_position[1]][1]
            val = now_chess[next_position[0]][next_position[1]][0]
            if self.belong == be:
                return val, False, next_position
            elif val >= 2:
                return val, True, next_position
            else:
                return False
        # 这里True,False 的包含了该棋子是不是属于落子方,如果属于，就返回True
        else:
            return False  # 对应该方向上没有己方棋子或者相邻的对手棋值为2

    def get_neighbor_3(self, now_chess, position: tuple, direction: int):
        """调用这个函数意味着落子方是self.belong,他正在分析将棋子落在not self.belong时的局势"""
        next_position = self.update_position(position, direction)
        while self.is_in_chess(next_position) and (now_chess[next_position[0]][next_position[1]][0] == 0) \
                and self.belong != now_chess[next_position[0]][next_position[1]][1]:
            next_position = self.update_position(next_position, direction)
        if self.is_in_chess(next_position):
            be = now_chess[next_position[0]][next_position[1]][1]
            val = now_chess[next_position[0]][next_position[1]][0]
            if self.belong != be:
                return val, False, self.belong, next_position
            elif val >= 2:
                return val, True, next_position
            else:
                return False
        # 这里True,False 的包含了该棋子是不是属于落子方,如果属于，就返回True
        else:
            return False  # 对应该方向上没有对方的棋子或者棋子值过小

    def get_neighbor_value(self, score, pos, direction, my_position_dict, now_chess, neibor_value):
        if neibor_value[(pos[0], pos[1], direction)] != -2:
            return neibor_value[(pos[0], pos[1], direction)]
        else:
            self.new_situation_score(score, pos, direction, my_position_dict, now_chess, neibor_value)
            return neibor_value[(pos[0], pos[1], direction)]

    def judge_is_safe(self, pos, direction, now_chess, neibor_value):  # 用来判断是否存在陷阱,pos上对应一个对手棋子
        if neibor_value[(pos[0], pos[1], direction)] != -2:
            return neibor_value[(pos[0], pos[1], direction)]
        else:
            next_position = self.update_position(pos, direction)
            while self.is_in_chess(next_position) and (now_chess[next_position[0]][next_position[1]][0] == 0) \
                    and self.belong != now_chess[next_position[0]][next_position[1]][1]:
                next_position = self.update_position(next_position, direction)
            if not self.is_in_chess(next_position):  # 该方向上没有棋子
                return -1
            elif self.belong == now_chess[next_position[0]][next_position[1]][1]:
                return -1
            else:
                neibor_value[(pos[0], pos[1], direction)] = now_chess[next_position[0]][next_position[1]][0]
                neibor_value[(next_position[0], next_position[1], self.get_direction(direction))] = \
                    now_chess[pos[0]][pos[1]][0]
                return neibor_value[(pos[0], pos[1], direction)]

    def mychess_is_safe(self, pos, belong, now_chess):  # 这是为new_expand写的判断棋子是否有靠山的函数
        for direction in range(4):
            next_position = self.update_position(pos, direction)
            while self.is_in_chess(next_position) and now_chess[next_position[0]][next_position[1]] == (0, belong):
                next_position = self.update_position(next_position, direction)
            if self.is_in_chess(next_position):
                if now_chess[next_position[0]][next_position[1]] == (now_chess[pos[0]][pos[1]][0] + 1, belong):  # 说明有靠山
                    return True
        else:
            return False

    def return_pos(self, belong):
        if belong:  # 返回先手方的遍历格子
            for i in range(self.height):
                for j in range(self.half_width):
                    yield i, j
        else:  # 返回后手方的遍历格子
            for i in range(self.height):
                for j in range(self.half_width, self.width):
                    yield i, j

    def is_in_chess(self, position):  # 用于判断该位置是否在棋盘中
        return (0 <= position[0] < self.height) and (0 <= position[1] < self.width)

    def is_mine(self, position):  # 判断该位置是否属于己方,返回True表示属于己方
        if self.belong:
            return 0 <= position[1] <= 3
        else:
            return 4 <= position[1] <= 7

    # is_in_chess进行了修改，之前width和height写反了  修改时间,5.18,20:49

    def my_chessman_score(self, value):  # 返回己方每个值对应多少得分,封装在函数中便于修改,在移动模式的代价函数中调用
        return self.value_list[value]
        # 没有直接返回2的value次方是为了鼓励AI尽可能合并大的

    """my_chessman_score进行了修改，原来是3*2**value,意识到
    如果是仅仅把value放在指数上不能实现鼓励AI合并的效果。
     之前产生AI合并仅仅是为了空位得分。修改时间,5.18, 21:02"""

    def rival_chessman_score(self, value: int):  # 返回对方每个值对应多少得分,封装在函数中便于修改，在移动模式的代价函数中调用
        return -self.value_list[value] // 2
        # 这里的返回分数修改为与my_chessman_score 一致了 5.24日
        # 权重下调 5.25日

    def be_occupied_score(self, value: int):
        """在己方棋盘被对方占据的情况返回的分数,返回的是一个负分，鼓励AI吃掉对方在自己位置的棋子
        封装在函数中便于修改，在移动模式的代价函数中调用"""
        return -self.basic_list[value]  # 权重下调 5.25日

    @staticmethod
    def occupy_score(value: int):
        """在对方棋盘被己方占据的情况返回的分数，返回一个正分，不再鼓励AI将棋子移回本方 5.25日"""
        return 2

    def threaten_score(self, value: int):
        """对应的是棋子存在吃对方和被对方吃的可能性时返回的分数,
        封在函数中便于修改，在get_neighbor_1函数中调用"""
        return self.basic_list[value]

    def put_chessman_score(self, value_1, value_2, value_3, value_4):
        score = 0
        if value_1 and value_2:
            if (value_1[1] != value_2[1]) and value_1[0] == value_2[0]:
                return float("inf")
            elif value_1[1] == value_2[1] and value_1[0] >= 4 and value_2[0] >= 4:
                score += self.basic_list[value_1[0]] + self.basic_list[value_2[0]]
        if value_3 and value_4:
            if value_3[1] != value_4[1] and value_3[0] == value_4[0]:
                return float("inf")
            elif value_3[1] == value_4[1] and value_3[0] >= 4 and value_4[0] >= 4:
                score += self.basic_list[value_3[0]] + self.basic_list[value_4[0]]
        return score

    def new_put_chess(self, value_1, value_2, value_3, value_4):  # value参数:(value,bool,pos) or False
        score = 0
        if value_1 and value_2:
            if (value_1[1] != value_2[1]) and value_1[0] == value_2[0]:
                return float("inf"), value_1[0], value_1[2] if value_1[1] else float("inf"), value_2[0], value_2[2]
                # 现在落子在分值相同的己方棋和对方棋之间，返回棋子的值，属于落子方的棋子的位置

            elif (not value_1[1]) and (not value_2[1]) and value_1[0] >= 4 and value_2[0] >= 4:
                score += self.basic_list[value_1[0]] + self.basic_list[value_2[0]]
                # 这里排除掉了落子位置是在两个己方棋子之间的情况(虽然概率极小)

        if value_3 and value_4:
            if value_3[1] != value_4[1] and value_3[0] == value_4[0]:
                return float("inf"), value_3[0], value_3[2] if value_3[1] else float("inf"), value_4[0], value_4[2]
                # 现在落子在分值相同的己方棋和对方棋之间，返回棋子的值，属于落子方的棋子的位置

            elif (not value_3[1]) and (not value_4[1]) and value_3[0] >= 4 and value_4[0] >= 4:
                score += self.basic_list[value_3[0]] + self.basic_list[value_4[0]]

        return score, max(value_1[0], value_2[0], value_3[0], value_4[0]), value_1[2]
        # 这里是为了与前面保持一致，其实后面两个返回值并不重要

    @staticmethod
    def update_position(position, direction):  # 在目前位置向给定方向移动一格
        if direction == 0:  # 对应向上移动
            return position[0] - 1, position[1]
        elif direction == 1:  # 对应向下移动
            return position[0] + 1, position[1]
        elif direction == 2:  # 对应向左移动
            return position[0], position[1] - 1
        elif direction == 3:  # 对应向右移动
            return position[0], position[1] + 1

    # update_function 进行了修改,之前坐标加减搞错了，导致AI在决策上出了一些问题 修改时间:5.18,20:35
    @staticmethod
    def get_direction(direction):
        if direction in [0, 1]:
            return 1 - direction
        else:
            return 5 - direction
